package com.noble.glamour.module.logic.socket;

import android.os.RemoteException;
import android.text.TextUtils;

import com.noble.glamour.module.logic.application.ModuleMgr;
import com.noble.glamour.module.logic.config.ServerTime;
import com.noble.glamour.module.logic.socket.listener.SocketConnectionListener;
import com.noble.glamour.module.logic.socket.logic.KeepAliveSocket;
import com.noble.glamour.module.logic.socket.stream.NetStream;
import com.noble.glamour.module.logic.socket.utils.HeartBeatUtil;
import com.noble.glamour.module.logic.socket.utils.IMUtils;
import com.noble.glamour.module.logic.socket.utils.SocketState;
import com.noble.glamour.module.util.TimerUtil;
import com.noble.library.log.PLogger;
import com.noble.library.log.PToast;
import com.noble.library.utils.JsonUtil;
import com.noble.meether.logic.common.bean.Server;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;

/**
 * 对内提供操作接口：
 * <p>
 * 负责即时通讯的重连机制
 */
public class AutoConnectMgr implements SocketConnectionListener {
    private final int DISCONNECT_TYPE_CONNECT_FAILED_TO_SEVER = 1;  // 连接服务器失败
    private final int DISCONNECT_TYPE_SEND_PACKET_TIMEOUT = 2;      // 发送数据超时

    // 线程池
    private final Executor connectionExecutor = Executors.newSingleThreadExecutor(); // 连接线程池
    private final Executor sendExecutor = Executors.newCachedThreadPool();  // 发送消息线程池
    private Disposable disposable;

    private static class SingletonHolder {
        static AutoConnectMgr instance = new AutoConnectMgr();
    }

    public static AutoConnectMgr getInstance() {
        return SingletonHolder.instance;
    }

    private KeepAliveSocket socket;
    private ICSCallback iCSCallback = null;

    private AutoConnectMgr() {
        socket = new KeepAliveSocket();
        socket.setSocketStateListener(this);
    }

    /**
     * 设置回调，和App进行交互。
     *
     * @param iCSCallback 回调实例。
     */
    public void setCallback(ICSCallback iCSCallback) {
        this.iCSCallback = iCSCallback;
    }

    /**
     * 登录用的信息。
     * 自动重连时需要用到，需要保存到内存中。Service重启后，需要再次调用login。<br>
     * 如果token是null或者""，则将现有Socket断开。否则则重新连接。
     */
    public void login() {
        if (heartBeating) return;//如果是保持心跳连接状态，就不再次登录
        TimerUtil.resetIncreaseTime();//主动重连接时重置间隔时间
        IMUtils.log("login: ------>发送连接服务器的消息");
        connect();
    }

    public SocketState getSocketStatus() {
        if (socket != null) {
            return socket.getSocketState();
        }
        return SocketState.INVALID;
    }

    /**
     * 退出登录，即断开即时通讯的连接。同时清除登录用的token。
     */
    public void logout() {
        IMUtils.log("logout: ------>与服务器断开连接");

        closeHeartBeating();
        loopHeartbeatStatus();
        connectionExecutor.execute(() -> {
            if (socket != null)
                socket.disconnect(true);
        });
    }

    /**
     * Socket发送数据包, 所有数据包的发送最终都通过此方法
     * 注意： 此方法不提供给外层调用
     */
    public void send(final NetData packet) {
        sendExecutor.execute(() -> {
            if (socket != null)
                socket.sendPacket(packet);
        });
    }

    /**
     * 发送心跳，可用来检测TCP连接状态
     */
    public void sendHeartData() {
        IMProxy.getInstance().send(NetStream.getHeartbeat());
    }

    /**
     * 重新登录
     */
    public void reLogin() {
        logout();
        login();
    }

    // ==================== 以下是关于内部自动连接维护的功能 =================

    /**
     * 以获取到的地址和秘钥登录通讯服务器
     */
    protected void connect() {
        connectionExecutor.execute(() -> {
            if (socket != null)
                socket.connect();
        });
    }

    /**
     * 重连，每次重连进行延时，防止刷服务器
     */
    protected void reConnect() {
        try {
            if (disposable != null && !disposable.isDisposed()) {
                return;
            }
            Observable<Long> timer = Observable.timer(getIncrementTime(), TimeUnit.MILLISECONDS);
            disposable = timer.subscribe(aLong -> {
                if (disposable != null) {
                    disposable.dispose();
                    disposable = null;
                    connect();
                }
            });
        } catch (Exception e) {
            IMUtils.logThrowable(e);
        }
    }

    /**
     * 重连递增时间，每次2秒
     */
    private int incrementTime = 0;

    /**
     * @return 请求发送的延时时长
     */
    private int getIncrementTime() {
        incrementTime = TimerUtil.increaseTime(TCPConstant.SOCKET_AUTO_CONNECT_Increment_Time, 60 * 1000, false);
        IMUtils.log("socket reconnect delayed time：" + incrementTime);
        return incrementTime;
    }

    // ========================================== 以下为心跳逻辑 =============================================
    private boolean heartBeating = false;   //是否需要发送心跳：长连接断线状态为false

    /**
     * 关闭心跳
     */
    public void closeHeartBeating() {
        heartBeating = false;
    }

    /**
     * 发送心跳消息
     */
    public void heartbeat() {
//        IMUtils.log("---heartbeat--->heartBeating：" + heartBeating);
        if (!heartBeating) return;
        if (socket == null) {
            closeHeartBeating();
            loopHeartbeatStatus();
        } else {
            send(NetStream.getHeartbeat());
        }
    }

    /**
     * 应用退出登录
     *
     * @param reason 退出原因：1[异地登陆踢下线]，2[密码验证失败，用户不存在等]，3[账号被封消息]
     */
    private void accountInvalid(int reason) {
        accountInvalid(reason, null);
    }

    private void accountInvalid(int reason, String content) {
        try {
            if (iCSCallback != null) iCSCallback.accountInvalid(reason, content);
        } catch (RemoteException e) {
            IMUtils.logThrowable(e);
        }
    }

    /**
     * 回送心跳状态变更
     */
    private void loopHeartbeatStatus() {
        try {
            if (iCSCallback != null) iCSCallback.heartbeatStatus(heartBeating);
        } catch (RemoteException e) {
            IMUtils.logThrowable(e);
        }
    }

    /**
     * Socket连接时，创建socket失败重连
     */
    private void onDisconnect(int type) {
        String disconnectType = "";
        switch (type) {
            case DISCONNECT_TYPE_CONNECT_FAILED_TO_SEVER:
                disconnectType = "连接服务器失败";
                break;
            case DISCONNECT_TYPE_SEND_PACKET_TIMEOUT:
                disconnectType = "发送数据超时";
                break;
        }

        //暂停心跳，开始断线重连
        closeHeartBeating();
        loopHeartbeatStatus();
        onStatusChange(TCPConstant.SOCKET_STATUS_Disconnect, "socket断开连接：" + disconnectType);
        reConnect();
    }

    /**
     * 将即时通讯中的状态变化发送到App中。
     *
     * @param type 类型。
     * @param msg  消息内容。
     */
    private void onStatusChange(final int type, final String msg) {
        try {
            if (iCSCallback != null) {
                iCSCallback.onStatusChange(type, msg);
            }
        } catch (Exception de) {
            IMUtils.logThrowable(de);
        }
    }

    // ======================================== 以下为 Socket 连接状态变化回调 （KeepAliveSocket中监听处理） ==============================

    @Override
    public void onSocketConnected() {
        onStatusChange(TCPConstant.SOCKET_STATUS_Connected, "socket连接服务器成功");
        TimerUtil.resetIncreaseTime();

        if (ModuleMgr.getLoginMgr().getUid() == 0 ||
                TextUtils.isEmpty(ModuleMgr.getLoginMgr().getAuth())) {
            return;
        }
        send(NetStream.getLoginData()); // 发送登录数据包
    }


    @Override
    public void onSocketConnecting() {
        onStatusChange(TCPConstant.SOCKET_STATUS_ConnectIng, "socket正在连接");
    }

    @Override
    public void onSocketConnectError() {
        onDisconnect(DISCONNECT_TYPE_CONNECT_FAILED_TO_SEVER);
    }

    @Override
    public void onSendPacketError(SocketState state, NetData failedData) {
        IMUtils.log("onSendPacketError:---->SocketState:" + state.name() + ",sender:" + failedData.getFromId() + ",content:" + failedData.getContent());
        reLogin();       // 数据包发送失败，进行重连
        try {
            if (iCSCallback != null) {
                iCSCallback.onSendMsgError(failedData);
            }
        } catch (Exception de) {
            PLogger.printThrowable(de);
        }
    }

    @Override
    public void onSocketDisconnectByError() {
        onDisconnect(DISCONNECT_TYPE_SEND_PACKET_TIMEOUT);
    }

    /**
     * CoreService进程中执行
     */
    @Override
    public void onReceivePacket(NetData data) {
        String msgBody = data.getMsgBody();
        ServerTime.setServerTime(data.getServerTime());//保存服务器时间戳
        IMUtils.log("---socket消息体：" + msgBody);

        switch (data.getMsgType()) {
            case TCPConstant.MSG_HOST_RESET:     // 重置Host服务地址
                String resetHostContent = data.getContent();
                if (TextUtils.isEmpty(resetHostContent)) return;
                new Server().parseJson(resetHostContent);
                reLogin();
                break;
            case TCPConstant.MSG_ID_HEART_INTERVAL: // 重置心跳间隔
                String content = data.getContent();
                if (TextUtils.isEmpty(content)) return;
                int heartbeat = JsonUtil.getJsonObject(content).optInt("Heart") * 1000;
                HeartBeatUtil.getInstance().resetHeartbeat(heartbeat);
                break;

            // socket注册登录成功
            case TCPConstant.MSG_ID_LOGIN_SUC:
                heartBeating = true;
                loopHeartbeatStatus();
                incrementTime = 0;
                onStatusChange(TCPConstant.SOCKET_STATUS_Login_Success, "socket用户登录成功：" + msgBody);
                break;

            case TCPConstant.MSG_ID_RE_LOGIN:   // 先退出，在重登录
                reLogin();
                break;

            // socket登录失败
            case TCPConstant.MSG_ID_LOGIN_FAIL:
                IMUtils.log("socket登录---->用户不存在");
                accountInvalid(2);
                break;

            case TCPConstant.MSG_ID_LOGIN_BLOCK:
                onStatusChange(TCPConstant.SOCKET_STATUS_Login_Fail, "socket用户登录失败：" + msgBody);
                IMUtils.log("socket登录---->用户已封号");
                PToast.showLong("您因特殊原因已被系统封号，如有疑问，请联系客服");
                accountInvalid(3, msgBody);
                break;

            case TCPConstant.MSG_ID_OTHER_PLACE:
                accountInvalid(1);
                break;
        }

        //抛出消息
        onMessage(data);
    }

    @Override
    public void onSocketDisconnectNormally() {
    }

    /**
     * 将即时通讯中收到消息通过ICSCallback抛出。
     */
    private void onMessage(NetData data) {
        try {
            if (iCSCallback != null) {
                iCSCallback.onMessage(data);
            }
        } catch (Exception de) {
            IMUtils.logThrowable(de);
        }
    }
}
